//
//  BLEManager.swift
//  ciotTest
//
//  Created by casdon on 2025/5/12.
//

import Foundation
import Combine
import CoreBluetooth

func convertToCChar(str: String) -> UnsafePointer<CChar>?
{
    let cStringMutable: UnsafeMutablePointer<CChar>? = strdup(str)
    let uuid: UnsafePointer<CChar>? = cStringMutable.map { UnsafePointer($0) }
    return uuid
}



enum BLEProvisioningState: Equatable {
    case idle                        // 初始状态，尚未开始
    case scanning                    // 正在扫描设备
    case deviceDiscovered           // 已发现目标设备
    case connecting                 // 正在连接设备
    case connected                  // 已连接
    case serviceDiscovering         // 正在发现服务
    case readyToProvision           // 准备开始配网
    case sendingCredentials         // 正在发送 WiFi 信息
    case awaitingResponse           // 等待设备确认配网结果
    case deviceVerified             // 设备已验证
    case wifiConnecting            // WiFi 正在连接
    case wifiConnected             // WiFi 已连接
    case success                    // 配网成功
    case failure(Error)             // 配网失败（可带错误信息）
    case disconnected               // 与设备断开
    case timeout                    // 配网超时
    
    static func == (lhs: BLEProvisioningState, rhs: BLEProvisioningState) -> Bool {
        switch (lhs, rhs) {
        case (.idle, .idle),
             (.scanning, .scanning),
             (.deviceDiscovered, .deviceDiscovered),
             (.connecting, .connecting),
             (.connected, .connected),
             (.serviceDiscovering, .serviceDiscovering),
             (.readyToProvision, .readyToProvision),
             (.sendingCredentials, .sendingCredentials),
             (.awaitingResponse, .awaitingResponse),
             (.success, .success),
             (.disconnected, .disconnected),
             (.timeout, .timeout),
             (.failure, .failure): // 忽略 error 内容
            return true
        default:
            return false
        }
    }
}


class CiotBLEProvisioner: NSObject, ObservableObject {

    @Published var wifiSSID: String
    @Published var wifiPass: String
    @Published var isConnected: Bool = false
    @Published var isSearching: Bool = false
    @Published var connectedPeripheral: Peripheral!
    
    var heartbeatCharacteristic: CBCharacteristic?
    private var heartbeatTimer: Timer? // 强引用持有
    
    var selectedPeripheral: Peripheral?
    
    var state: BLEProvisioningState = .idle {
        didSet {
            if state != oldValue {
                print("[Ciot DEBUG Mobile] State changed to \(state)")
                delegate?.updateState(state, provisioner: self)
            }
        }
    }
    
    private var centralManager: CBCentralManager
    weak var delegate: CiotManagerDelegate?
    
    var bleState: CBManagerState = .unknown
    let pid = "CIOT-SDK"
    let did = "CIOT-iOS-SDK"
    let license = "b893c0f734c548e9068680bb3b1ade70e9eb263d3a3828524613e3590e602fa7000174114667255243a5a6312296000136873bf7e4de459bb167818a558637be93ad447f6a0addab"
    let appID:String = "TestAPPID"
    let userID:String = "TestUserID"
    let deviceID:String = "TestDeviceID"

    var foundPeripherals: [Peripheral] = []
    var foundServices: [Service] = []
    var foundCharacteristics: [Characteristic] = []
    
    // [uuid: peripheral]
    var blePeripheralMap = [String: CBPeripheral]()
    // [uuid: rssi]
    var blePeripheralRSSIMap = [String: NSNumber]()
    
    var model: String? = nil
    
    private var sendingChar: CBCharacteristic? = nil
    private var receivingChar: CBCharacteristic? = nil
    

    
    override init() {
        self.wifiSSID = ""
        self.wifiPass = ""
        self.centralManager = CBCentralManager(delegate: nil, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
        super.init()
        centralManager.delegate = self
    }
    
    init(ssid: String, pass: String) {
        wifiSSID = ssid
        wifiPass = pass
        centralManager = CBCentralManager(delegate: nil, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
        super.init()
        centralManager.delegate = self
    }
    
    private  func connectPeripheralWithId(_ id: UUID?) {
        let peripheral = self.foundPeripherals.first { $0.id == id }
        guard let peripheral = peripheral else { return }
        connectedPeripheral = peripheral
        
        if connectedPeripheral == nil { return }
        selectPeripheral(connectedPeripheral!)
        let options: [String: Any] = [
            CBConnectPeripheralOptionNotifyOnConnectionKey: true,
            CBConnectPeripheralOptionNotifyOnDisconnectionKey: true,
            CBConnectPeripheralOptionNotifyOnNotificationKey: true,
            // 启用L2CAP长连接（如需）
            CBConnectPeripheralOptionEnableTransportBridgingKey: true
        ]
        centralManager.connect(connectedPeripheral.peripheral, options: options)
    }
    
    func selectPeripheral(_ selectPeripheral: Peripheral) {
        selectedPeripheral = selectPeripheral
    }
    
    func disconnectPeripheral() {
        guard let connectedPeripheral = connectedPeripheral else { return }
        centralManager.cancelPeripheralConnection(connectedPeripheral.peripheral)
    }
    
    
    // MARK: - Ciot SDK
    func c_mobile_ble_scan_device(name: UnsafePointer<Int8>?, uuid: UnsafePointer<Int8>?) -> Ciot_res {
        let scanOption = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
        
        if uuid != nil{
            centralManager.scanForPeripherals(withServices: [ CBUUID(string: CIOT_GATTS_SERVICE_UUID_CIOT_MSG_STR) ], options: scanOption)
        }else{
            centralManager.scanForPeripherals(withServices: nil, options: scanOption)
        }
        state = .scanning
        return Ciot_OK
    }
    
    func c_mobile_ble_connect(uid: UnsafePointer<Int8>?, mac: UnsafePointer<Int8>?) -> Ciot_res
    {
        print("[Ciot DEBUG Mobile] ciot_mobile_ble_connect")
        
        guard let uid = uid else {
            print("指针无效")
            return Ciot_Err
        }
        let strUid = String(cString: uid)
        
        if let uuid = UUID(uuidString: strUid) {
            connectPeripheralWithId(uuid)
            return Ciot_OK
        }
       
        return Ciot_Err
    }
    
    func c_mobile_ble_set_mtu_request(mtu: Int32) -> Ciot_res {
        print("[Ciot DEBUG Mobile] ciot_mobile_ble_set_mtu_request")
        return Ciot_OK
    }
    
    func c_mobile_ble_set_rw_char(readChar: UnsafePointer<Int8>?, writeChar: UnsafePointer<Int8>?) -> Ciot_res
    {
        print("[Ciot DEBUG Mobile] c_mobile_ble_set_rw_char")
        return Ciot_OK
    }
    
    func c_mobile_ble_message_send(data: UnsafePointer<UInt8>?, len: Int) -> Ciot_res {
        
        guard let data = data, len > 0 else {
            return Ciot_Err
        }
        
        let bleData:Data = Data(bytes: data, count: len)
        
        if let sendingChar = sendingChar
        {
            connectedPeripheral.peripheral.writeValue( bleData, for: sendingChar , type: .withResponse)
        }
        
        return Ciot_OK
    }
    
    func c_mobile_ble_discovered_notify(uid: UnsafePointer<Int8>?, mac: UnsafePointer<Int8>?, pid: UnsafePointer<Int8>?, clinkStatus: Ciot_clink_connection_status, random: Int32) -> Ciot_res {
        // TODO: random
        let swift_pid = String(cString: pid!)
        let swift_uid = String(cString: uid!)
        print("[Ciot DEBUG Mobile] notifyDeviceDiscovered \(swift_pid) \(swift_uid) \(clinkStatus)")
        
        guard let peripheral = blePeripheralMap[swift_uid] , let rss = blePeripheralRSSIMap[swift_uid] else{
            return Ciot_Err
        }
    
        let foundPeripheral: Peripheral = Peripheral(_peripheral:peripheral, _uid: swift_uid, _pid: swift_pid, _rssi: rss, _name: "", _discoverCount: 1)
        
        if let index = foundPeripherals.firstIndex(where: { $0.peripheral.identifier.uuidString == peripheral.identifier.uuidString }) {
            if foundPeripherals[index].discoverCount % 50 == 0 {
                foundPeripherals[index].pid = swift_pid
                foundPeripherals[index].uid = swift_uid
                foundPeripherals[index].rssi = Int(truncating: rss)
                foundPeripherals[index].discoverCount += 1
            } else {
                foundPeripherals[index].discoverCount += 1
            }
        } else {
            foundPeripherals.append(foundPeripheral)
        }
        delegate?.didDiscoverPeripherals(foundPeripherals, provisioner: self)
        state = .deviceDiscovered
        return Ciot_OK
    }
}


// MARK: - CBCentralManagerDelegate
extension CiotBLEProvisioner: CBCentralManagerDelegate{
    //每当蓝牙中央设备的状态发生变化时调用，例如蓝牙被关闭、权限发生改变、蓝牙初始化完成等。你可以在这里检查蓝牙的状态，并根据不同的状态执行相应的操作
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            print("蓝牙已开启")
        case .poweredOff:
            print("蓝牙已关闭")
        case .resetting:
            print("蓝牙正在重置")
        case .unauthorized:
            print("蓝牙权限未授权")
        case .unknown:
            print("蓝牙状态未知")
        case .unsupported:
            print("设备不支持蓝牙")
        @unknown default:
            print("未知状态")
        }
        bleState = central.state
    }

    // 当扫描到外围设备时调用该方法。你可以获取到扫描到的设备以及设备的一些广告数据（如设备名称、服务等）
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
    {
        if RSSI.intValue >= 0 { return }
        
        blePeripheralMap[peripheral.identifier.uuidString] = peripheral
        blePeripheralRSSIMap[peripheral.identifier.uuidString] = RSSI
        
        let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? nil
        var _name = ""
        
        if peripheralName != nil {
            _name = String(peripheralName!)
        } else if peripheral.name != nil {
            _name = String(peripheral.name!)
        }
        print(#function, _name, peripheral.identifier.uuidString, RSSI.intValue)
        if _name != "Casdon" && advertisementData["kCBAdvDataLocalName"] as? String != "Casdon" { return }
        
        if let mData = advertisementData["kCBAdvDataManufacturerData"] as? Data
        {
            let _ = delegate?.scanResult(uuid: peripheral.identifier.uuidString, rspData: mData, ssid: "", encrypted: false)
        }
    }
    
    // 当成功连接到一个外围设备时调用。在这里你可以开始与设备进行交互（如发现服务、读取特征值等）
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
    {
        guard let connectedPeripheral = connectedPeripheral else { return }
        print("[Ciot DEBUG Mobile] 连接成功 ")
        isConnected = true
        state = .connected
        connectedPeripheral.peripheral = peripheral
        connectedPeripheral.peripheral.delegate = self
        connectedPeripheral.peripheral.discoverServices(nil)
    
        // 定时发送空数据包维持连接

        let heartbeatUUID = CBUUID(string: "FFE1") // 自定义心跳特征UUID
        let heartbeatCharacteristic = CBMutableCharacteristic(
            type: heartbeatUUID,
            properties: [.writeWithoutResponse], // 允许无响应写入
            value: nil, // 动态数据
            permissions: [.writeable] // 允许写入
        )
        
        self.heartbeatCharacteristic = heartbeatCharacteristic
        
        self.heartbeatTimer = Timer.scheduledTimer(
            withTimeInterval: 1.0, // 间隔建议 10-30 秒
            repeats: true
        ) { _ in
            guard let characteristic = self.heartbeatCharacteristic else { return }
            peripheral.writeValue(Data([0x00]), for: characteristic, type: .withoutResponse)
        }
        RunLoop.current.add(self.heartbeatTimer!, forMode: .common)
        
        let _ = delegate?.connected(uuid: connectedPeripheral.peripheral.identifier.uuidString, ssid: "", pass: "")
    }
    
    // 当连接外围设备失败时调用。在这里你可以处理连接失败的情况
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("[Ciot DEBUG Mobile] 连接设备失败: \(peripheral.name ?? "未知设备"), 错误: \(error?.localizedDescription ?? "无详细信息")")
        guard let connectedPeripheral = connectedPeripheral else { return }
        centralManager.cancelPeripheralConnection(connectedPeripheral.peripheral)
    }

    // 当连接的外围设备断开时调用。在这里你可以处理断开连接后的操作
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
    {
        print("[Ciot DEBUG Mobile] 断开连接")
        if let error = error {
            print("[Ciot DEBUG Mobile] code Error: \(error.localizedDescription)")
        } else {
            print("[Ciot DEBUG Mobile] Disconnected successfully")
        }
        let _ = delegate?.disConnected(uuid: peripheral.identifier.uuidString, ssid: "", pass: "")
    }
    
    // 当发现外围设备的服务时调用。在这里你可以获取到设备的服务列表
    func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
        // Handle state restoration if needed
        print("[Ciot DEBUG Mobile] Central manager will restore state: \(dict)")
    }
}
// MARK: - CBPeripheralDelegate
extension CiotBLEProvisioner : CBPeripheralDelegate{
    //当外设发现了服务时会被调用
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        peripheral.services?.forEach { service in
            let setService = Service(_uuid: service.uuid, _service: service)
            foundServices.append(setService)
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
    //当外设的某个服务下的特征被发现时，会调用此方法。它会传递发现的特征及相关服务
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard error == nil else {
             self.state = .failure(error!)
             return
         }
        service.characteristics?.forEach { characteristic in
            let setCharacteristic: Characteristic = Characteristic(_characteristic: characteristic,
                                                                   _description: "",
                                                                   _uuid: characteristic.uuid,
                                                                   _readValue: "",
                                                                   _service: characteristic.service!)
            foundCharacteristics.append(setCharacteristic)
            peripheral.readValue(for: characteristic)

            if characteristic.uuid.uuidString == CIOT_GATTS_CHAR_UUID_CIOT_RECEIVE_STR
            {
                sendingChar = characteristic
            }

            if characteristic.uuid.uuidString == CIOT_GATTS_CHAR_UUID_CIOT_SEND_STR
            {
                receivingChar = characteristic
                peripheral.setNotifyValue(true, for: characteristic)
            }
            
            if ((sendingChar != nil) && (receivingChar != nil))
            {
               state = .readyToProvision
                let _ = delegate?.wr_ready()
            }
        }
    }
    // 当外设某个特征的值被更新时，会调用此方法。你可以在此方法中获取最新的特征值
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: (any Error)?) {
        guard let characteristicValue = characteristic.value else { return }
        print("[Ciot DEBUG Mobile] --update char value \(characteristic.uuid.uuidString) -")

        if let index = foundCharacteristics.firstIndex(where: { $0.uuid.uuidString == characteristic.uuid.uuidString }) {
            
            if characteristic.uuid.uuidString == "2A29" || characteristic.uuid.uuidString == "2A24"
            {
                foundCharacteristics[index].readValue = String(data: characteristicValue , encoding: .utf8)!
                
                if characteristic.uuid.uuidString == "2A24"
                {
                    model = foundCharacteristics[index].readValue
                }
            }
            else if (characteristic.uuid.uuidString == CIOT_GATTS_CHAR_UUID_CIOT_RECEIVE_STR) || (characteristic.uuid.uuidString == CIOT_GATTS_CHAR_UUID_CIOT_SEND_STR)
            {
                print("[Ciot DEBUG Mobile] Receive message fron ble\(characteristic.uuid.uuidString) ")
                if !characteristicValue.isEmpty
                {
                    let _ = delegate?.message_received(characteristicValue: characteristicValue)
                }
            }
            else
            {
                foundCharacteristics[index].readValue = characteristicValue.map({ String(format:"%02x", $0) }).joined()
            }
        }
    }
    
    //当数据被成功写入到外设的某个特征时会调用此方法，可以通过它检查写入操作是否成功。
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?)
    {
        print("[Ciot DEBUG Mobile] !! WriteValue OK !!")
    }
    //当特征的通知状态发生改变时调用，比如启用或禁用了某个特征的通知
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?)
    {
        print("[Ciot DEBUG Mobile] !! didUpdateNotificationStateFor !!")
    }
}



class Service: Identifiable {
    var id: UUID
    var uuid: CBUUID
    var service: CBService

    init(_uuid: CBUUID,
         _service: CBService) {
        
        id = UUID()
        uuid = _uuid
        service = _service
    }
}

class Peripheral: Identifiable {
    var id: UUID = UUID()
    var peripheral: CBPeripheral
    var pid: String
    var rssi: Int
    var discoverCount: Int
    var uid: String
    var cover: String
    var clinkStatus: String
    var name: String
    
    init(_peripheral: CBPeripheral,
         _uid: String,
         _pid: String,
         _rssi: NSNumber,
         _name: String,
         _discoverCount: Int) {
        id = UUID()
        uid = _uid
        pid = _pid
        peripheral = _peripheral
        rssi = _rssi.intValue
        discoverCount = _discoverCount + 1
        name = _name
        cover = ""
        clinkStatus = "idle"
    }
}

class Characteristic: Identifiable {
    var id: UUID
    var characteristic: CBCharacteristic
    var description: String
    var uuid: CBUUID
    var readValue: String
    var service: CBService

    init(_characteristic: CBCharacteristic,
         _description: String,
         _uuid: CBUUID,
         _readValue: String,
         _service: CBService) {
        
        id = UUID()
        characteristic = _characteristic
        description = _description
        uuid = _uuid
        readValue = _readValue
        service = _service
    }
}
